package com.sajus.web.billing.ejb.bean;

import java.util.Collections;
import java.util.List;
import java.util.Set;

import javax.annotation.Resource;
import javax.ejb.SessionContext;
import javax.ejb.TransactionAttribute;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;
import javax.persistence.EntityManager;
import javax.persistence.Parameter;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceException;
import javax.persistence.PersistenceUnitUtil;
import javax.persistence.TypedQuery;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;

import com.sajus.web.billing.ejb.data.model.PersistentObject;
import com.sajus.web.billing.ejb.data.model.TransactionContext;
import com.sajus.web.billing.ejb.domain.DomainObject;

public class BaseBean {

	@PersistenceContext(name="MySqlDS", unitName="MySqlDS")
	protected EntityManager em;

	@Resource private SessionContext context;

	@Resource private UserTransaction utx;

	@AroundInvoke
	public Object interceptorMethod(InvocationContext ctx) throws java.lang.Exception {
		/* report time spent in the call */
		String log = buildLogString(ctx);

		boolean markForRollbackOnlyAllowed = true;
		try {
			/* in order to check if operates within a container transaction call getRollbackOnly.
			 * if an IllegalStateException is thrown then there is no transaction and eliminates the 
			 * need to call markForRollback. If outside a transaction and we call setRollbackOnly it will 
			 * throw an exception at that point.
			 */
			context.getRollbackOnly();			
		} catch (IllegalStateException e) {
			markForRollbackOnlyAllowed = false;
		}


		Object res;
		TransactionContext context = null;
		try {
			/* create the transactionContext */
			context = new TransactionContext( this.getEntityManager() );
			/* call the actual method of the bean */
			res = ctx.proceed();
			return res;
		} catch (Exception pe) { 
			/* make sure it does not commit if within a transaction when it is not a policy exception
			 * for policy exceptions the transaction should still commit. One example is the user login, 
			 * invalid credentials will generate a policy exception but the login attempts count needs to 
			 * be increased */
			boolean isAllowCommit = isAllowCommit(pe);
			try {
				if (markForRollbackOnlyAllowed == true && isAllowCommit == false) {
					markRollbackOnly();
				}
			} catch (Throwable t) {
			}
			markRollbackOnly();
			throw pe;
		} finally {
			/* if there is a request then close it 
			 * and release possible DB connection
			 */
			if (context != null) {
				context.close();
			}			

		}
	}

	private boolean isAllowCommit(Throwable e) {
		return false;
	}

	private String buildLogString(InvocationContext ctx) {
		String log = "#call:";
		log += ctx.getMethod().getName() + " ( ";
		Object[] parameters = ctx.getParameters();
		if (parameters != null && parameters.length != 0) {
			for (Object object : parameters) {
				log+= ( (object == null) ? "null" : object.toString() )+ ", "; 
			}
		}
		log += ")";
		return log;
	}

	public EntityManager getEntityManager() {
		return em;
	}

	private void markRollbackOnly() {
		context.setRollbackOnly();
	}

	public void persist( DomainObject o) {
		try{
			persist( o.getDelegate( getEntityManager() ) );
		} catch ( Exception e ){
			e.printStackTrace();
		}
	}

	protected void persist( PersistentObject o) throws Exception {
		if ( o == null ) {
			return;
		}
		//		System.out.println("Persisting object" + o );
		getEntityManager().persist( o );
		o.setPersisted( true );
		System.out.println("Persisted object " + o );

	}

	public void update(PersistentObject item) {
		if (item == null) {
			return;
		}
		EntityManager em = getEntityManager();
		PersistenceUnitUtil persistenceUtil = em.getEntityManagerFactory().getPersistenceUnitUtil();
		PersistentObject existingItem = em.find(item.getClass(), persistenceUtil.getIdentifier( item ));
		if (existingItem == null) {
			try {
				persist( item );
			} catch ( Exception e ){
				e.printStackTrace();
			}
		} else {
			em.merge( item );
		}
	}

	protected <T> T getSingleResult(String namedQuery, Class<T> cls, Object ... params ) {
		TypedQuery<T> query = getEntityManager().createNamedQuery(namedQuery, cls);
		Set<Parameter<?>> parameters = query.getParameters();
		if (params != null) {
			for(int i = 0; i < params.length; i++) {
				query.setParameter(i + 1, params[i]);
			}
		}
		try {
			return query.getSingleResult() ;
		} catch (PersistenceException p) {
			return null;
		}

	}

	public <T> List<T> getResultList(String namedQuery, Class<T> cls, Object ... params ) {
		TypedQuery<T> query = getEntityManager().createNamedQuery(namedQuery, cls);
		int index = 1;
		if (params != null) {
			for(Object param : params) {
				query.setParameter(index++, param);
			}
		}
		try {
			return query.getResultList();
		} catch (PersistenceException p) {
			p.printStackTrace();
			return Collections.emptyList();
		}
	}

	public <T> T getEntityByPK(Class<T> cls, Object id) {
		return getEntityManager().find(cls, id);
	}


}
